/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 *   gbx-emu - debugger.cpp                                                *
 *   gbx-emu homepage: http://code.google.com/p/gbx-emu/                   *
 *   Copyright (C) 2009 olejl77@gmail.com                                  *
 *                                                                         *
 *   This program is free software: you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation, either version 3 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the          *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>. *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "debugger.h"
#include "gameboy.h"
#include "cpu.h"
#include "memory.h"
#include "../gui/mainwindow.h"
#include "../gui/debuggerwidget.h"

Debugger::Debugger(Gameboy *ptr)
{
    _gb = ptr;
    _cpu = NULL;
    _dbgWidget = NULL;
    _ui = NULL;
    _mem = NULL;
    _enabled = false;

    _lastAction = dbgStep;   // Set to step so that the debugger is initially paused

    // Initialize variables
    _ptrBreakpoint = 0;
}

Debugger::~Debugger()
{
    _gb = NULL;
    _cpu = NULL;
    _dbgWidget = NULL;
    _ui = NULL;
    _mem = NULL;
}

void Debugger::dbgStart()
{
    if (_enabled) {
        _dbgWidget = _ui->startDebugger();
        if (_dbgWidget != NULL) {
            _dbgWidget->setMemory(_mem);
            _dbgWidget->setCpu(_cpu);
            _dbgWidget->setDebugger(this);
            _dbgWidget->initDebugger();
        } else {
            throw "[Debugger] Debugger GUI not initialized!";
        }
    }
}

void Debugger::enableDebugger(bool enabled)
{
    _enabled = enabled;

    // Update status in Cpu and Gameboy
    if (_cpu == NULL) {
        throw "[Debugger] Cpu not initialized!!";
    }
    _cpu->enableDebugger(_enabled);
}

void Debugger::debuggerAction(u32 action)
{
    const u16 *pc;
    u32 size;
    u8 opcode;
    u16 tmp;
    _lastAction = action;
    switch (action) {
    case dbgDisabled:
        enableDebugger(false);
        break;
    case dbgBreak:
        break;
    case dbgStep:
        _condition.wakeOne();
        break;
    case dbgNext:
        pc = _cpu->getPC();
        opcode = _mem->read(*pc);
        size = opcodeLength[opcode];
        tmp = *pc + size;
        if (addBreakpoint(*pc + size, BPT_FLAG_ENABLED | BPT_FLAG_NEXT)) {
            _condition.wakeAll();
        } else {
            _log->Msg(WARNING_LEVEL, LOG_CPU, "Failed to set breakpoint!");
        }
        break;
    case dbgRun: case dbgTrace:
        _condition.wakeAll();
        break;
    case dbgReset:
        _cpu->resetCpu();
        _condition.wakeOne();
        break;
    default:
        _log->Msg(ERROR_LEVEL, LOG_CPU, "Illegal debugger action!");
        break;
    }
}

void Debugger::debuggerUpdate(u16 pc)
{
    switch (_lastAction) {
    case dbgDisabled:
        _log->Msg(ERROR_LEVEL, LOG_CPU, "Illegal lastAction!");
        break;
    case dbgBreak: case dbgStep: case dbgReset:
        _dbgWidget->updatePC(pc);
        _condition.wait(&_mutex);
        break;
    case dbgTrace:
        _dbgWidget->updatePC(pc);
        if (checkBreakpoint(*_cpu->getPC())) {
            _dbgWidget->updatePC(pc);
            _condition.wait(&_mutex);
        } else {
        _gb->milliSleep(40);
        }
        break;
    case dbgRun: case dbgNext:
        if (checkBreakpoint(*_cpu->getPC())) {
            _dbgWidget->updatePC(pc);
            _condition.wait(&_mutex);
        }
        break;
    default:
        _log->Msg(ERROR_LEVEL, LOG_CPU, "Illegal lastAction!");
        break;
    }
}

bool Debugger::addBreakpoint(u16 addr)
{
    bool ret = true;
    if (_ptrBreakpoint != MAXBP) {
        bpArray[_ptrBreakpoint].address = addr;
        bpArray[_ptrBreakpoint].flags = BPT_FLAG_ENABLED | BPT_FLAG_EXEC;
        _ptrBreakpoint++;
    } else {
        ret = false;
    }
    return ret;
}

bool Debugger::addBreakpoint(u16 addr, u32 flags)
{
    bool ret = true;
    if (_ptrBreakpoint != MAXBP) {
        bpArray[_ptrBreakpoint].address = addr;
        bpArray[_ptrBreakpoint].flags = flags;
        _ptrBreakpoint++;
    } else {
        ret = false;
    }
    return ret;
}

void Debugger::removeBreakpoint(u32 num)
{
    for (u32 i = num; i < (_ptrBreakpoint - 1); i++)
    {
        bpArray[i].address = bpArray[i + 1].address;
        bpArray[i].flags = bpArray[i + 1].flags;
    }
    _ptrBreakpoint--;
}

void Debugger::enableBreakpoint(u32 num)
{
    bpArray[num].flags |= BPT_FLAG_ENABLED;
}

void Debugger::disableBreakpoint(u32 num)
{
    bpArray[num].flags &= ~BPT_FLAG_ENABLED;
}

bool Debugger::checkBreakpoint(u16 addr)
{
    bool ret = false;
    for (u32 i = 0; i < _ptrBreakpoint; i++)
    {
        if ((bpArray[i].address <= addr) && ((bpArray[i].flags & BPT_FLAG_NEXT) > 0)) {
            removeBreakpoint(i);
            ret = true;
            break;
        } else if (bpArray[i].address == addr) {
            ret = true;
            break;
        }
    }
    return ret;
}

u32 Debugger::getNumBreakpoint()
{
    return _ptrBreakpoint;
}

breakpoint Debugger::getNumBreakpoint(u32 num)
{
    return bpArray[num];
}

bool Debugger::addBreakpointStruct(breakpoint *bp)
{
    bpArray[_ptrBreakpoint] = *bp;
    _ptrBreakpoint++;
    return true;
}


